/// @file MainApp.cpp
/// @brief the main gui application class, extended from the
/// MainWindow_ui and holds all the UI and our main gl window

#include "MainApp.h"
#include "Configuration.h"

#include "ui_MainWindow.h"

MainApp::MainApp
                (
                    QWidget* _parent
                ):
                QMainWindow(_parent),
                m_ui(new Ui::MainWindow)
{
    // setup the user interface
    m_ui->setupUi(this);

    //create a new instance of the world and give it an initial aspect ratio
    m_simulationManager = new SimulationManager(this);

    m_ui->s_mainGridLayout->addWidget(m_simulationManager, 0, 0, 4, 2);

    //setup signals/slots
    setupConnections();
}

MainApp::~MainApp()
{
    delete m_ui;
}

void MainApp::setupConnections()
{
    //read values from simulation managers and set up control initial values here
    Cloth* cloth = m_simulationManager->getCloth();
    ObstacleManager* obstacleManager = m_simulationManager->getObstacleManager();

    //--------------------------------------TAB : CLOTH
    //set dimensions
    m_ui->spin_cloth_height->setValue(cloth->getHeight());
    m_ui->spin_cloth_width->setValue(cloth->getWidth());
    m_ui->spin_cloth_verticalParticleCount->setValue(cloth->getVerticalParticleCount());
    m_ui->spin_cloth_horizontalParticleCount->setValue(cloth->getHorizontalParticleCount());

    //set material
    int index = m_ui->cmb_cloth_material->findText(QString(cloth->getCurrentClothMaterialName().c_str()));
    m_ui->cmb_cloth_material->setCurrentIndex(index);

    m_ui->sld_cloth_stiffness->setValue(toSliderValue(1, 1, cloth->getStiffness()));
    m_ui->sld_cloth_dampiness->setValue(toSliderValue(0.01, 1, cloth->getDamping()));

    //select drawing mode of cloth
    m_ui->chk_cloth_wireframe->setChecked(cloth->getDrawWireframe());

    //--------------------------------------TAB : SIM
    //select default integration type
    m_ui->cmb_sim_integrationType->setCurrentIndex((int)cloth->getIntegrationType());

    //set timestep
    m_ui->sld_sim_timestep->setValue(toSliderValue(0.001, 10, cloth->getTimestep()));

    //--------------------------------------TAB : FORCE
    m_ui->chk_force_gravity->setChecked(cloth->getEnableGravity());
    m_ui->group_wind->setChecked(cloth->getEnableWind());
    m_ui->chk_spring_structural->setChecked(cloth->getEnableHookeStructural());
    m_ui->chk_spring_stretch->setChecked(cloth->getEnableHookeStretch());
    m_ui->chk_spring_bend->setChecked(cloth->getEnableHookeBend());

    m_ui->sld_wind_strength->setValue(toSliderValue(0.005, 5, cloth->getWindStrength()));
    m_ui->spin_wind_direction_x->setValue(cloth->getWindDirection().m_x);
    m_ui->spin_wind_direction_y->setValue(cloth->getWindDirection().m_y);
    m_ui->spin_wind_direction_z->setValue(cloth->getWindDirection().m_z);

    //--------------------------------------TAB : OBSTACLE
    m_ui->group_obstacle->setChecked(obstacleManager->getObstacleEnabled());
    m_ui->group_cube->setChecked(obstacleManager->getCubeEnabled());
    m_ui->chk_cube_bbox->setChecked(obstacleManager->getDrawBBox());
    m_ui->group_sphere->setChecked(obstacleManager->getSphereEnabled());

    m_ui->spin_cube_position_x->setValue(obstacleManager->getCubePosition().m_x);
    m_ui->spin_cube_position_y->setValue(obstacleManager->getCubePosition().m_y);
    m_ui->spin_cube_position_z->setValue(obstacleManager->getCubePosition().m_z);

    m_ui->spin_cube_size_x->setValue(obstacleManager->getCubeDimension().m_x);
    m_ui->spin_cube_size_y->setValue(obstacleManager->getCubeDimension().m_y);
    m_ui->spin_cube_size_z->setValue(obstacleManager->getCubeDimension().m_z);

    m_ui->spin_sphere_position_x->setValue(obstacleManager->getSpherePosition().m_x);
    m_ui->spin_sphere_position_y->setValue(obstacleManager->getSpherePosition().m_y);
    m_ui->spin_sphere_position_z->setValue(obstacleManager->getSpherePosition().m_z);

    m_ui->spin_sphere_radius->setValue(obstacleManager->getSphereRadius());

    //--------------------------------------TAB : INTERNAL
    //toggle trace particles & set value and max value
    m_ui->group_trace->setChecked(cloth->getEnableInternalTrace());
    m_ui->spin_internal_particle_no->setValue(cloth->getInternalTraceParticleId());
    m_ui->spin_internal_particle_no->setMaximum((cloth->getHorizontalParticleCount() * cloth->getVerticalParticleCount()) - 1);

    m_ui->chk_internal_traceNeighbours->setChecked(cloth->getEnableInternalTraceNeighbours());

    m_ui->chk_internal_pinned->setChecked(!cloth->getInternalTraceParticle()->getMoveable());

    //--------------------------------------GLOBAL CAMERA
    //setup camera zoom control and signal here
    //read camera z value from config file and set it to the slider
    ngl::Camera m_camera = Configuration::initialiseCamera(1.0);
    m_ui->sld_cameraZoom->setValue(-m_camera.getEye().m_z);
    connect(m_ui->sld_cameraZoom, SIGNAL(valueChanged(int)), m_simulationManager, SLOT(updateCameraZoom(int)));

}

void MainApp::keyPressEvent(
                               QKeyEvent* _event
                            )
{
    // this method is called every time the main window recives a key event.
    this->setFocus(Qt::ActiveWindowFocusReason);

    switch (_event->key())
    {
        case Qt::Key_Escape : QApplication::exit(EXIT_SUCCESS); break;

        case Qt::Key_M : showFullScreen(); break;
        case Qt::Key_N : showNormal(); break;

        default : break;
    }

    m_simulationManager->keyPress(_event);
}

int MainApp::toSliderValue
                        (
                            const float _lowerTrue,
                            const int _lowerSlider,
                            const float _lowerX
                        )
{
    float ratio = (float)_lowerSlider / _lowerTrue;
    return (int)(_lowerX * ratio);
}

float MainApp::fromSliderValue
                        (
                            const float _lowerTrue,
                            const int _lowerSlider,
                            const int _lowerX)
{
    float ratio = (float)_lowerSlider / _lowerTrue;
    return (float)_lowerX / ratio;
}

void MainApp::reInitialiseSimulation(const ConstrainedScenario _scenario)
{
    //reinialise cloth particles
    float timestep = fromSliderValue(0.001, 10, m_ui->sld_sim_timestep->value());

    m_simulationManager->getCloth()->reInitialiseParticles
            (
                m_ui->spin_cloth_height->value(),
                m_ui->spin_cloth_width->value(),
                m_ui->spin_cloth_verticalParticleCount->value(),
                m_ui->spin_cloth_horizontalParticleCount->value(),
                timestep,
                (IntegrationType)m_ui->cmb_sim_integrationType->currentIndex(),
                _scenario
            );

    //update obstacle manager objects
//    ngl::Vector cubePos = ngl::Vector(m_ui->spin_cube_position_x->value(), m_ui->spin_cube_position_y->value(), m_ui->spin_cube_position_z->value());
//    ngl::Vector cubeSize = ngl::Vector(m_ui->spin_cube_size_x->value(), m_ui->spin_cube_size_y->value(), m_ui->spin_cube_size_z->value());
//    ngl::Vector spherePos = ngl::Vector(m_ui->spin_sphere_position_x->value(), m_ui->spin_sphere_position_y->value(), m_ui->spin_sphere_position_z->value());
//    int sphereRadius = m_ui->spin_sphere_radius->value();

//    m_simulationManager->getObstacleManager()->setCubePosition(cubePos);
//    m_simulationManager->getObstacleManager()->setCubeDimension(cubeSize);
//    m_simulationManager->getObstacleManager()->setSpherePosition(spherePos);
//    m_simulationManager->getObstacleManager()->setSphereRadius(sphereRadius);

    //refresh pinned info for trace particle
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}


//SIMULATION SLOTS

void MainApp::on_chk_cloth_wireframe_clicked(const bool _checked)
{
    //update simulation drawing mode
    m_simulationManager->getCloth()->setDrawWireframe(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmb_cloth_material_currentIndexChanged(const int _index)
{
    //get the selected text
    std::string text = m_ui->cmb_cloth_material->itemText(_index).toStdString();

    //update simulation cloth material
    m_simulationManager->getCloth()->setCurrentClothMaterial(text);

    //damping and stiffness are changed -> update them on the gui
    m_ui->sld_cloth_stiffness->setValue(toSliderValue(1, 1, m_simulationManager->getCloth()->getStiffness()));
    m_ui->sld_cloth_dampiness->setValue(toSliderValue(0.01, 1, m_simulationManager->getCloth()->getDamping()));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_sld_cloth_stiffness_valueChanged(const int _value)
{
    //update stiffness of sim
    m_simulationManager->getCloth()->setStiffness(fromSliderValue(1, 1, _value));
}

void MainApp::on_sld_cloth_dampiness_valueChanged(const int _value)
{
    //update damping of sim
    m_simulationManager->getCloth()->setDamping(fromSliderValue(0.01, 1, _value));
}

void MainApp::on_chk_force_gravity_clicked(const bool _checked)
{
    //update gravity toggle of sim
    m_simulationManager->getCloth()->setEnableGravity(_checked);
}

void MainApp::on_group_wind_clicked(const bool _checked)
{
    //update wind toggle of sim
    m_simulationManager->getCloth()->setEnableWind(_checked);
}

void MainApp::on_chk_spring_structural_clicked(const bool _checked)
{
    //update structural spring toggle of sim
    m_simulationManager->getCloth()->setEnableHookeStructural(_checked);
}

void MainApp::on_chk_spring_stretch_clicked(const bool _checked)
{
    //update shear spring toggle of sim
    m_simulationManager->getCloth()->setEnableHookeStretch(_checked);
}

void MainApp::on_chk_spring_bend_clicked(const bool _checked)
{
    //update flex spring toggle of sim
    m_simulationManager->getCloth()->setEnableHookeBend(_checked);
}

void MainApp::on_sld_wind_strength_valueChanged(const int _value)
{
    //update wind strenth of sim
    m_simulationManager->getCloth()->setWindStrength(fromSliderValue(0.005, 5, _value));
}

void MainApp::on_spin_wind_direction_x_valueChanged(const int _value)
{
    //update wind direction x of sim
    m_simulationManager->getCloth()->setWindDirection(ngl::Vector(_value, m_simulationManager->getCloth()->getWindDirection().m_y, m_simulationManager->getCloth()->getWindDirection().m_z));
}

void MainApp::on_spin_wind_direction_y_valueChanged(const int _value)
{
    //update wind direction y of sim
    m_simulationManager->getCloth()->setWindDirection(ngl::Vector(m_simulationManager->getCloth()->getWindDirection().m_x, _value, m_simulationManager->getCloth()->getWindDirection().m_z));
}

void MainApp::on_spin_wind_direction_z_valueChanged(const int _value)
{
    //update wind direction z of sim
    m_simulationManager->getCloth()->setWindDirection(ngl::Vector(m_simulationManager->getCloth()->getWindDirection().m_x, m_simulationManager->getCloth()->getWindDirection().m_y, _value));
}

void MainApp::on_group_obstacle_clicked(const bool _checked)
{
    //update obstacle manager toggle
    m_simulationManager->getObstacleManager()->setObstacleEnabled(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_group_cube_clicked(const bool _checked)
{
    //update obstacle manager cube toggle
    m_simulationManager->getObstacleManager()->setCubeEnabled(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_group_sphere_clicked(const bool _checked)
{
    //update obstacle manager sphere toggle
    m_simulationManager->getObstacleManager()->setSphereEnabled(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_chk_cube_bbox_clicked(const bool _checked)
{
    //update obstacle manager draw bbox toggle
    m_simulationManager->getObstacleManager()->setDrawBBox(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_sphere_radius_valueChanged(const int _value)
{
    //update obs manager sphere radius -> not advisable
    m_simulationManager->getObstacleManager()->setSphereRadius(_value);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_sphere_position_x_valueChanged(const int _value)
{
    //update sphere pos x of obs manager
    m_simulationManager->getObstacleManager()->setSpherePosition(ngl::Vector(_value, m_simulationManager->getObstacleManager()->getSpherePosition().m_y, m_simulationManager->getObstacleManager()->getSpherePosition().m_z));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_sphere_position_y_valueChanged(const int _value)
{
    //update sphere pos y of obs manager
    m_simulationManager->getObstacleManager()->setSpherePosition(ngl::Vector(m_simulationManager->getObstacleManager()->getSpherePosition().m_x, _value, m_simulationManager->getObstacleManager()->getSpherePosition().m_z));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_sphere_position_z_valueChanged(const int _value)
{
    //update sphere pos z of obs manager
    m_simulationManager->getObstacleManager()->setSpherePosition(ngl::Vector(m_simulationManager->getObstacleManager()->getSpherePosition().m_x, m_simulationManager->getObstacleManager()->getSpherePosition().m_y, _value));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_cube_size_x_valueChanged(const int _value)
{
    //update cube size x of obs manager
    m_simulationManager->getObstacleManager()->setCubeDimension(ngl::Vector(_value, m_simulationManager->getObstacleManager()->getCubeDimension().m_y, m_simulationManager->getObstacleManager()->getCubeDimension().m_z));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_cube_size_y_valueChanged(const int _value)
{
    //update cube size y of obs manager
    m_simulationManager->getObstacleManager()->setCubeDimension(ngl::Vector(m_simulationManager->getObstacleManager()->getCubeDimension().m_x, _value, m_simulationManager->getObstacleManager()->getCubeDimension().m_z));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_cube_size_z_valueChanged(const int _value)
{
    //update cube size z of obs manager
    m_simulationManager->getObstacleManager()->setCubeDimension(ngl::Vector(m_simulationManager->getObstacleManager()->getCubeDimension().m_x, m_simulationManager->getObstacleManager()->getCubeDimension().m_y, _value));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_cube_position_x_valueChanged(const int _value)
{
    //update cube pos x of obs manager
    m_simulationManager->getObstacleManager()->setCubePosition(ngl::Vector(_value, m_simulationManager->getObstacleManager()->getCubePosition().m_y, m_simulationManager->getObstacleManager()->getCubePosition().m_z));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_cube_position_y_valueChanged(const int _value)
{
    //update cube pos y of obs manager
    m_simulationManager->getObstacleManager()->setCubePosition(ngl::Vector(m_simulationManager->getObstacleManager()->getCubePosition().m_x, _value, m_simulationManager->getObstacleManager()->getCubePosition().m_z));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_cube_position_z_valueChanged(const int _value)
{
    //update cube pos z of obs manager
    m_simulationManager->getObstacleManager()->setCubePosition(ngl::Vector(m_simulationManager->getObstacleManager()->getCubePosition().m_x, m_simulationManager->getObstacleManager()->getCubePosition().m_y, _value));

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_group_trace_clicked(const bool _checked)
{
    //toggle enable trace on particle
    m_simulationManager->getCloth()->setEnableInternalTrace(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_spin_internal_particle_no_valueChanged(const int _value)
{
    //toggle id of particle under neighbour trace
    m_simulationManager->getCloth()->setInternalTraceParticleId(_value);

    //update pin info about new particle traced
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_chk_internal_traceNeighbours_clicked(const bool _checked)
{
    //toggle tracing of particle with neighbours
    m_simulationManager->getCloth()->setEnableInternalTraceNeighbours(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_chk_internal_pinned_clicked(const bool _checked)
{
    //toggle pin of current particle
    m_simulationManager->getCloth()->pinInternalTraceParticle(_checked);

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_internal_pinAll_clicked()
{
    //pin all particles
    m_simulationManager->getCloth()->pinAll(true);

    //modify trace particle pin info
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_internal_unpinAll_clicked()
{
    //unpin all particles
    m_simulationManager->getCloth()->pinAll(false);

    //modify trace particle pin info
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_internal_pinRow_clicked()
{
    //pin particles from current row
    m_simulationManager->getCloth()->pinRow(true);

    //modify trace particle pin info
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_internal_unpinRow_clicked()
{
    //unpin particles from current row
    m_simulationManager->getCloth()->pinRow(false);

    //modify trace particle pin info
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_internal_pinColumn_clicked()
{
    //unpin particles from current row
    m_simulationManager->getCloth()->pinColumn(true);

    //modify trace particle pin info
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_internal_unpinColumn_clicked()
{
    //unpin particles from current row
    m_simulationManager->getCloth()->pinColumn(false);

    //modify trace particle pin info
    m_ui->chk_internal_pinned->setChecked(!m_simulationManager->getCloth()->getInternalTraceParticle()->getMoveable());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_2DTopView_clicked()
{
    //setup top view
    m_simulationManager->setup2DTopView();
}

void MainApp::on_cmd_2DFrontView_clicked()
{
    //setup front view
    m_simulationManager->setup2DFrontView();
}

void MainApp::on_cmd_reInitialiseSimulation_clicked()
{
    //re initialise sim with current scenario
    reInitialiseSimulation(m_simulationManager->getCloth()->getScenario());
}

void MainApp::on_cmd_sim_tablecloth_clicked()
{
    //re initialise sim with tablecloth scenario
    reInitialiseSimulation(TABLECLOTH);
}

void MainApp::on_cmd_sim_curtain_clicked()
{
    //re initialise sim with light curtain scenario
    reInitialiseSimulation(LIGHT_CURTAIN);
}

void MainApp::on_cmd_sim_shelter_clicked()
{
    //re initialise sim with shelter scenario
    reInitialiseSimulation(SHELTER);
}

void MainApp::on_cmd_sim_flag_clicked()
{
    //re initialise sim with flag scenario
    reInitialiseSimulation(FLAG);
}

void MainApp::on_cmd_sim_banner_clicked()
{
    //re initialise sim with banner scenario
    reInitialiseSimulation(BANNER);
}

void MainApp::on_cmd_sim_solidCurtain_clicked()
{
    //re initialise sim with solid curtain scenario
    reInitialiseSimulation(SOLID_CURTAIN);
}

void MainApp::on_cmd_singleStep_clicked()
{
    //single step sim
    m_simulationManager->getCloth()->updateParticles(m_simulationManager->getObstacleManager());

    //refresh view
    m_simulationManager->updateGL();
}

void MainApp::on_cmd_autoMove_clicked()
{
    //toggle auto movement

    //change the name of the command button depending on the state of automove
    m_ui->cmd_autoMove->setText(!m_simulationManager->getCloth()->getEnableAutoCloth() ? "AutoMove Stop" : "AutoMove Start");

    //toggle the automove state
    m_simulationManager->getCloth()->toggleEnableAutoCloth();
}
